#!/usr/bin/env perl -w
# Time-stamp: <21 Jun 02 15:54:23 giron1> 

=head1 NAME

comment2tex.perl - Create Latex comment from C++ source code

=head1 SYNOPSIS

 comment2tex.perl [-help] detail|external|general source1.h [source2.h ...]

=head1 DESCRIPTION

This programs creates a latex document containing the description of
classes or any other embedded comment. This document created is not a
standalone one, it has to be included in a main document where a header
and tailer are provided.

=head1 SEE ALSO

For more precision about the packages used in this program
L<OPT_EXT_HTML/OPT_EXT>

=head1 AUTHOR

 This is mostly the work of Ogawa-san. Franck Giron added some
 documentation and extended the possible commands with general.

 $Id: comment2tex.perl,v 1.1 2007/10/08 09:26:04 schweitz Exp $

=head1 LOG

 $Log: comment2tex.perl,v $
 Revision 1.1  2007/10/08 09:26:04  schweitz
 version 2.4, lib vergessen

 Revision /main/6  22-Jan-2003.10:03:59  giron1
 Changing header to /usr/bin/env perl.
 
 Revision /main/5  27-Jun-2002.13:24:53  giron1
 Change of paragraph formating for .cpp file processed alone.
 The header is different for the first entry.
 The size of the font is reduced to scriptsize for flows.
 
 Revision /main/4  19-Jun-2002.11:44:17  giron1
 Added more flexibility in latex code generation.
 
 Revision /main/3  23-Apr-2002.16:31:28  giron1
 suppression of protection of ^. Addition of filtering option
 for keys.
 
 Revision /main/2  19-Apr-2002.16:33:12  giron1
 Extension of the code to generate more simplified general
 documentation without too many sections or subsections.
 Pod documentation also added to this file.
 

=cut



#----------------------------------------------------------------------
# Packages 
#----------------------------------------------------------------------
#use strict;
use OPTIONS;
use exargs;
use Env;
use CommentBlock;

#----------------------------------------------------------------------
# Global variables
#----------------------------------------------------------------------

# Latex Standard Sectioning
my %LSS = ('-1' => ['\part{','}'], '0' => ['\chapter{','}'], '1' => ['\section{',"}\n"], '2' => ['\subsection{','}'],
		   '3' => ['\subsubsection{','}'], '4' => ['\paragraph{','}'], '5' => ['\subparagraph{','}']);

# Preferred Latex Section Description used depending on the code
my %PLSD = ('detail' => {'s1' => $LSS{'1'},'s2' => $LSS{'2'} ,
						 'keyf1' => ['',''],'descf1' => [' : \textit{','}'],
						 'keyf2' => [' ',''],'descf2' => ['\\\\\textit{','}'],
						 'Descriptionf1' => ['\begin{description}','\item[',']\ \\\\','\end{description}'],
						 'Descriptionf2' => ['\begin{description}','\item[',']\ \\\\','\end{description}'],
						 'Rulef1' => ['',''],
						 'Rulef2' => ['\rule{','cm}{1pt}']}, 
			'external' => {'s1' => $LSS{'1'},'s2' => $LSS{'2'},
						   'keyf1' => ['',''],'descf1' => [' : \textit{','}'],
						   'keyf2' => [' ',''],'descf2' => ['\\\\\textit{','}'],
						   'Descriptionf1' => ['\begin{description}','\item[',']\ \\\\','\end{description}'],
						   'Descriptionf2' => ['\begin{description}','\item[',']\ \\\\','\end{description}'],
						   'Rulef1' => ['',''],
						   'Rulef2' => ['\rule{','cm}{1pt}']}, 
			'general' => {'s1' => $LSS{'2'},'s2' => $LSS{'4'},
						  'keyf1' => ['\\mbox{','}'],'descf1' => [' : ',''],
						  'keyf2' => ['\\ding{111} ',''],'descf2' => [' : \textsf{','}'],
						  'Descriptionf1' => ['\begin{entry}','\item[',']','\end{entry}'],
						  'Descriptionf2' => ['\begin{entry}','\item[',']','\end{entry}'],
						  'Rulef1' => ['\rule{','cm}{1pt}'], 
						  'Rulef2' => ['','']},

		   );

#----------------------------------------------------------------------
# MAIN
#----------------------------------------------------------------------

sub Main {

  # Define the set of options

  my $OptList = new OPTIONS;

  $OptList->Rule('Do' => 0, 'Usage' => 'add a rule between paragraph');
  $OptList->debug('Do' => 0, 'Usage' => 'debuging mode');
  $OptList->notusedkey('string' => '', 'Usage' => "<string>\t: not used keys in entry description");
  $OptList->TabStop('string' => 8,'Usage' => "<int>\t: assumed spaces equivalent size of tab");
  $OptList->TableStep('string' => 0.01,'Usage' => "<float>\t: step of the indentation");
  $OptList->TableScale('string' => 0.90,'Usage' => "<float>\t: scale of the indentation");
  $OptList->TableAbsLen('string' => 11,'Usage' => "<float>\t: absolute length or table");
  $OptList->EscapeKey('string' => '@@@','Usage' => "<string>\t: string used for escaping processing");
  $OptList->eh('string' => '.h', 'Usage' => 'extension for C/C++ header files');
  $OptList->sections('string' => '', 'Usage' => '<string>\t: sections to be parsed in code');

  # Command processing
  $OptList->GetOpt || die "Check your options\n";

  # Check the arguments
  my $numargs = @ARGV;

  my $usage = ($numargs <= 1) ;

  if ($usage) {
	$OptList->Usage("detail|external|general source1.h");
  }

  my $code = shift(@ARGV);
  my $file_h;
  my @cb_h;
  my @cb_cc;
  my @sections;

  # define the code given as argument as an option for further processing
  $OptList->code('string' => $code, 'Usage' => 'processing type');

  # automatically preset some options if not defined externally
  if ($code eq 'detail') {
	if ($OptList->{'sections'}{'string'} eq '') { @sections = qw(public protected private helper);}
	else {@sections = split /,/,$OptList->{'sections'}{'string'};}
	$OptList->{'Rule'}{'Do'} = 1;
	$OptList->{'notusedkey'}{'string'} = '' unless ($OptList->{'notusedkey'}{'string'} ne '');
  } elsif ($code eq 'external') {
	if ($OptList->{'sections'}{'string'} eq '') {@sections = qw(public);}
	else {@sections = split /,/,$OptList->{'sections'}{'string'};}
	$OptList->{'Rule'}{'Do'} = 1;
	$OptList->{'notusedkey'}{'string'} = '' unless ($OptList->{'notusedkey'}{'string'} ne '');
  } elsif ($code eq 'general') {
	if ($OptList->{'sections'}{'string'} eq '') {@sections = qw(public helper);}
	else {@sections = split /,/,$OptList->{'sections'}{'string'};}
	$OptList->{'Rule'}{'Do'} = 1;
	$OptList->{'notusedkey'}{'string'} = 'PARAMETER,REVISION,LOG,RETURN' unless ($OptList->{'notusedkey'}{'string'} ne '');
  }
  else {
	warn "$code: unknown key. It should be 'detail', 'external' or 'general'\n";
	exit(1);
  }

  my $eh = $OptList->{'eh'}{'string'};

  for $file_h (@ARGV) {
	if ($file_h !~ m/.*$eh$/) {
	  warn "Warning: $file_h: should be *$eh\n";
	}

	$OptList->{'debug'}{'Do'} and warn "GetCommentBlocks $eh ...\n";
	@cb_h = &CommentBlock::GetCommentBlocks($OptList,$file_h);
	$OptList->{'debug'}{'Do'} and warn "#cb_h = $#cb_h\n";

	$OptList->{'debug'}{'Do'} and warn "EscapeCharacters and Parse $eh ...\n";
	for (@cb_h) {
	  $_->{text} = &EscapeCharacters($OptList,$_->{text});
	  $_->Parse($OptList) if ($_->{mark} eq '@');
	}
	#@cb_h = &ParseFlow(@cb_h);

	# only try to open .cpp or .cc file if it is not already
	# given as the input file
	if ($file_h !~ m/\.cpp/ and $file_h !~ m/\.cc/ ) {
	  $OptList->{'debug'}{'Do'} and warn "ReadCxxFile ...\n";
	  @cb_cc = ($code eq 'detail' or $code eq 'general') ? &ReadCxxFile($OptList,$file_h) : ();
	  $OptList->{'debug'}{'Do'} and warn "#cb_cc = $#cb_cc\n";
	
	  $OptList->{'debug'}{'Do'} and warn "EscapeCharacters and Parse .cpp ...\n";
	  for (@cb_cc) {
		$_->{text} = &EscapeCharacters($OptList,$_->{text});
		$_->Parse($OptList) if ($_->{mark} eq '@');
	  }

	  $OptList->{'debug'}{'Do'} and warn "MergeCB ...\n";
	  &MergeCB($OptList, \@cb_h, \@cb_cc);
	}

	# don't have to continue if no comments could be found
	next if ($#cb_h < 0);

	$OptList->{'debug'}{'Do'} and warn "ParseFlow ...\n";
	@cb_h = &ParseFlow($OptList,@cb_h);
	$OptList->{'debug'}{'Do'} and warn "#cb_h = $#cb_h\n";

	# eventually insert a ruler before each section
	if ($OptList->{'Rule'}{'Do'} || $OptList->{'Rule'}{'Do'} eq 'true') {
	  if ($PLSD{$code}{'Rulef1'}[0] ne '' or $PLSD{$code}{'Rulef1'}[1] ne '') {
		&TexOut($OptList, 0, $PLSD{$code}{'Rulef1'}[0]. $OptList->{'TableAbsLen'}{'string'} . $PLSD{$code}{'Rulef1'}[1] . "\n");
	  }
	}

	$OptList->{'debug'}{'Do'} and warn "OutHeader ...\n";
	&OutHeader($OptList, \@cb_h, $PLSD{$code}{'s1'}[0], $PLSD{$code}{'s1'}[1], '', '');
	#warn "MergeCB: NUM CB: " . $#cb_h . "\n";

	$OptList->{'debug'}{'Do'} and warn "Processing sections ...\n";
	foreach $sec (@sections) {
	  $OptList->{'debug'}{'Do'} and warn "SEC ==> $sec\n";
	  my $sec_cb;
	  @sec_cb = grep($_->{section} eq $sec, @cb_h);
	  $OptList->{'debug'}{'Do'} and warn "#sec_cb ==>", $#sec_cb, "\n";
	  while ($#sec_cb >= 0) {
		$cb = shift @sec_cb;
		if ($cb->{mark} ne '@') {
		  # ERROR
		} 
		# eventually insert a ruler between each subsection
		if ($OptList->{'Rule'}{'Do'} || $OptList->{'Rule'}{'Do'} eq 'true') {
		  if ($PLSD{$code}{'Rulef2'}[0] ne '' or $PLSD{$code}{'Rulef2'}[1] ne '') {
			&TexOut($OptList, 0, $PLSD{$code}{'Rulef2'}[0]. $OptList->{'TableAbsLen'}{'string'} . $PLSD{$code}{'Rulef2'}[1] . "\n");
		  }
		} 
		&OutItemKey($OptList, 0, $cb, $sec, $PLSD{$code}{'s2'}[0],$PLSD{$code}{'s2'}[1] , '', '');
	  }
	}
  }
}
#
#    ParseFlow() --- Combine a sequence of flow into flow chart
#
sub ParseFlow {
  my($optl) = shift;
  my(@orgcb) = @_;
  my(@newcb);
  my(@tmpcb);
  
  while ($#orgcb >= 0) {
	# Skip @ comments
	while ($#orgcb >= 0 && $orgcb[0]->{mark} ne '#') {
	  push(@newcb, shift @orgcb);
	}
	@tmpcb = ();
	while ($#orgcb >= 0 && $orgcb[0]->{mark} eq '#') {
	  push(@tmpcb, shift @orgcb);
	}
	if ($#tmpcb >= 0) {
	  if ($#newcb < 0) {
		warn "ERROR: No @ comment for the flow in $tmpcb[0]->{file}: " . 
		      substr(join(' ', split("\n", $tmpcb[0]->{text})), 0, 30) . "...\n";
		exit 1;
	  }
	  $OptList->{'debug'}{'Do'} and warn "FLOW for " .  $newcb[$#newcb]->{title_pair}->GetKey() . "\n";
	  $OptList->{'debug'}{'Do'} and warn join(' ', $newcb[$#newcb]->{pair}->GetKeys()) . "\n";
	  $newcb[$#newcb]->{pair}->Add('FLOW', &GetFlow($optl,@tmpcb));
	  $OptList->{'debug'}{'Do'} and warn join(' ', $newcb[$#newcb]->{pair}->GetKeys()) . "\n";
	}
  }
  @newcb;
}

#
#   MergeCB --- Merge two CBs into one
#
#   DESCRIPTION:
#	MergeCB(cb1, cb2) combine cb2 into cb1 based on the title
#	of each comment.
#
sub MergeCB {
  my($optl, $cb_h, $cb_ccp) = @_;
  my @cb_cc = @{$cb_ccp};
  my @found_cb;
  my $cur_cb;
  my $cbi;
  my $i = 0;
  my $otheri = 0;
  my $n = 0;
  
  $optl->{'debug'}{'Do'} and warn "MergeCB: NUM CB(h):   " . $#{$cb_h} . "\n";
  $optl->{'debug'}{'Do'} and warn "MergeCB: NUM CB(cpp): " . $#{$cb_ccp} . "\n";
  $cbi = 0;
  while ($cbi <= $#{$cb_h}) {
	$cur_cb = $$cb_h[$cbi++];
	next if ($cur_cb->{mark} ne '@');
	#
	# Find same section in Cxx file
	#
	$optl->{'debug'}{'Do'} and warn "start FIND: $cbi :" . $cur_cb->{mark} . "\n";
	$optl->{'debug'}{'Do'} and warn "      text: $cbi :" . $cur_cb->{text} . "\n";
	$i = &FindSameTitle($optl,$cur_cb, @cb_cc);
	$optl->{'debug'}{'Do'} and warn "end   FIND: $cbi\n";
	if ($i >= 0) {
	  $optl->{'debug'}{'Do'} and warn "FOUND: $i\n";
	  #
	  #	Count following comments except '@'
	  #
	  $n = 1;
	  while ($i + $n <= $#cb_cc && $cb_cc[$i + $n]->{mark} ne '@') {
		$n++;
	  }
	  #
	  # Merge them
	  #
	  @found_cb = splice(@cb_cc, $i, $n);
	  foreach $key ($found_cb[0]->{pair}->GetKeys()) {
		$optl->{'debug'}{'Do'} and warn "MergeCB: $key\n";
		$cur_cb->{pair}->Add($key, $found_cb[0]->{pair}->GetDesc($key));
	  }
	  splice(@{$cb_h}, $cbi, 0, @found_cb[1..$#found_cb]);
	}
	$optl->{'debug'}{'Do'} and warn "check for  : " . $cur_cb->{title_pair}->GetKey(0) . "\n";
	$optl->{'debug'}{'Do'} and warn "start FIND2: $cbi :" . $cur_cb->{mark} . "\n";
	if (($otheri = &FindSameTitle($optl,$cur_cb, @cb_cc)) >= 0) {
	  warn "Warning: $cb_cc[0]->{file}: Duplicated comment for title: " . $cur_cb->{title_pair}->GetKey(0) . "\n";
	}
	$optl->{'debug'}{'Do'} and warn "end   FIND2: $cbi\n";
  }
  if ($#cb_cc >= 0) {
	warn "Warning: ",$cb_cc[0]->{file},": ",scalar(@cb_cc)," followings lines are not used:\n";
	for (@cb_cc) {
	  my $warning = "    ($_->{mark}) ";
	  if ($_->{mark} eq '@') {
		$warning .= $_->{title_pair}->GetKey(0);
	  } else {
		$warning .= substr(join(' ', split("\n", $_->{text})), 0, 50) . "...";
	  }
	  warn "$warning\n";
	}
  }
  #warn "MergeCB: NUM CB: " . $#{$cb_h} . "\n";
}

#
#   FindSameTitle --- Find the cb having the same title in the array 
#
#	RETURN:	the index or -1
#
sub FindSameTitle {
    my ($optl,$ref, @cbs) = @_;
    my $key = &Title2Key($ref->{title_pair}->GetKey(0));
    my $i;

    $optl->{'debug'}{'Do'} and warn "SEARCH Title: " . $ref->{title_pair}->GetKey(0) . "\n";
    $optl->{'debug'}{'Do'} and warn "SEARCH key  : $key in $#cbs elements\n";
    for ($i = 0; $i <= $#cbs; $i++) {
#	$optl->{'debug'}{'Do'} and warn "$i, $cbs[$i], FIND=$cbs[$i]->{title_pair}\n";
#	$optl->{'debug'}{'Do'} and warn "$i: $cbs[$i]->{text} \n";
	if ($cbs[$i]->{mark} eq '@' && $key eq &Title2Key($cbs[$i]->{title_pair}->GetKey(0))) {
	    $optl->{'debug'}{'Do'} and warn "FOUND  Title: $key\n";
		$optl->{'debug'}{'Do'} and warn "text        :$i: $cbs[$i]->{text} \n";
		return $i;
	}
    }
    return -1;
}

#
#    ReadCxxFile --- Read *.cc or *.cpp file corresponding to the *.h file
#
sub ReadCxxFile {
    my $optl = shift;
    my $file_h = shift;
    my $file_cc = $file_h;
    my @cb;

	my $eh = $optl->{'eh'}{'string'};
    $file_cc =~ s/$eh$/.cc/;
    if (! -f $file_cc) {
	  $file_cc = $file_h;
	  $file_cc =~ s/$eh$/.cpp/;
    }
    if (-f $file_cc) {
	  #print "READ $file_cc\n";
	  @cb = &CommentBlock::GetCommentBlocks($optl,$file_cc);
	  #print "READ 0..$#cb\n";
    }
    @cb;
}

###############################################################################
#
#	OUTPUT TOOLS
#
###############################################################################

#
#   OutHeader --- output comment block as Class header
#
sub OutHeader {
  my($optl,$cb_array, $title_begin, $title_end, $body_begin, $body_end) = @_;
#  my $head = ${$cb_array}[0];
  my($key, $desc);

  my $code = $optl->{'code'}{'string'};

  my @headcb = ();
  if ($code eq 'general') {
	@headcb = grep($_->{section} eq 'header', @$cb_array);
  }
  else {
	push @headcb, ${$cb_array}[0];
  }

  # This is the header counter, it is assumed that the first one
  # contains the general description of the program
  my $headcount = 0;
  foreach $head (@headcb) {

	$optl->{'debug'}{'Do'} and warn "OutHeader: section: ",$head->{section},"\n";

    return if (not defined($head->{section}) or $head->{section} ne 'header');

    # title
    $key = $head->{title_pair}->GetKey(0);
    $desc = $head->{title_pair}->GetDesc($key);

    if ($code ne 'general') {$desc =~ s/^\s*/\\\\/;}

	$optl->{'debug'}{'Do'} and warn "OutHeader: key  :",$key,"\n";
	$optl->{'debug'}{'Do'} and warn "OutHeader: desc :",$desc,"\n";
	$optl->{'debug'}{'Do'} and warn "OutHeader: count:",$headcount,"\n";

	# special treatment for first header section
	if ($headcount == 0) {
	  &TexOut($optl, 0, $title_begin
			  . $PLSD{$code}{'keyf1'}[0]
			  . &RemoveFollowingBlanks("$key")
			  . $PLSD{$code}{'keyf1'}[1]
			  . $PLSD{$code}{'descf1'}[0]
			  . &RemoveFollowingBlanks("$desc")
			  . $PLSD{$code}{'descf1'}[1]
			  . $title_end);
	  &TexOut($optl, 0, $body_begin);
	  &OutDesc($optl, 0, $head->{pair},'Descriptionf1');
	  &TexOut($optl, 0, $body_end);
	}
	# standard treatment for other header sections
	else {
	  &OutItemKey($optl, 0, $head, 'header', $LSS{'4'}[0],$LSS{'4'}[1] , $body_begin, $body_end);
	}
	$headcount++;
  }
}

#
#   OutItemKey --- Output comment block as key-description pair
#	In:
#	    $idt	depth of the indentation
#	    $cb		comment block
#
sub OutItemKey {
    my($optl, $idt, $cb, $sec, $title_begin, $title_end, $body_begin, $body_end) = @_;
    my($key, $desc);

    # title
    my $code = $optl->{'code'}{'string'};
    $key = $cb->{title_pair}->GetKey(0);
    $desc = $cb->{title_pair}->GetDesc($key);
	return if (not defined($key) or not defined($desc)); 
    $desc =~ s/^\s*//;

	if ($code ne 'general') {$title_begin .= $sec;}

    if ($title_begin eq 'NOTITLE') {
	  &OutDesc($optl,$idt + 4, $cb->{pair},'Descriptionf2');
    } else {
	  if ($desc =~ m/^\s*$/) {
	    &TexOut($optl,$idt, $title_begin
				. $PLSD{$code}{'keyf2'}[0]
				. &RemoveFollowingBlanks("$key")
				. $PLSD{$code}{'keyf2'}[1]
				. $title_end);
	  } else {
	    &TexOut($optl,$idt, $title_begin
				. $PLSD{$code}{'keyf2'}[0]
				. &RemoveFollowingBlanks("$key") 
				. $PLSD{$code}{'keyf2'}[1]
				. $PLSD{$code}{'descf2'}[0]
				. &RemoveFollowingBlanks("$desc")
				. $PLSD{$code}{'descf2'}[1]
				. $title_end);
	  }
	  &TexOut($optl,$idt, $body_begin);
	  &OutDesc($optl,$idt + 4, $cb->{pair},'Descriptionf2');
	  &TexOut($optl,$idt, $body_end);
    }
}
#
#
#   OutDesc --- output key-desc pair hash using \description
#	In:
#	     $idt	depth of the indentation
#	     $pair	reference to the key-desc hash table
#
sub OutDesc {
	my($optl,$idt, $pair,$descformat) = @_;
	my($key, $desc);
	my(%subpair);
	my($i);

    return if ($pair->GetNKeys() < 0);

	my $pattern = $optl->{'notusedkey'}{'string'};
	my $filterkeys = ($pattern ne '');
	$pattern =~ s/,/|/g;
	my @keyslist = $pair->GetKeys();
	$optl->{'debug'}{'Do'} and warn "keys list = @keyslist\n";
	my @notusedkeys = split(',',$optl->{'notusedkey'}{'string'});
	$optl->{'debug'}{'Do'} and warn "not used  = @notusedkeys\n";
	my @match = grep m/$pattern/g,@keyslist;
	$optl->{'debug'}{'Do'} and warn "pattern   = $pattern, match = @match\n";
	$optl->{'debug'}{'Do'} and warn "#match  = $#match, #keyslist = $#keyslist\n";
	return if ($#match == $#keyslist);

    my $code = $optl->{'code'}{'string'};

    if ($pair->GetKey(0) eq '__HEAD_TEXT__') {
	  &TexOut($optl, $idt, $pair->GetDesc('__HEAD_TEXT__'));
	  $pair->Shift();
    }
    &TexOut($optl, $idt, $PLSD{$code}{$descformat}[0]);
    foreach $key ($pair->GetKeys()) {
	  $desc = $pair->GetDesc($key); 
	  $optl->{'debug'}{'Do'} and warn "key = $key\n";
	  $optl->{'debug'}{'Do'} and warn "desc =\n$desc\n";
	  $optl->{'debug'}{'Do'} and warn "match = @match\n";
	  next if ($filterkeys and grep($key eq $_, @match) > 0);
	  $optl->{'debug'}{'Do'} and warn "key = $key\n";
	  #if ($key =~ /\@$/) {
	  if (defined(%{$desc})) {
		$key =~ s/\@$//;
		$optl->{'debug'}{'Do'} and warn "===>$key\n";
		if ($desc->GetKey(0) eq '__HEAD_TEXT__') {
		  &TexOut($optl,$idt, $PLSD{$code}{$descformat}[1] . "$key" . $PLSD{$code}{$descformat}[2] . "\n\n");
		} else {
		  &TexOut($optl,$idt, $PLSD{$code}{$descformat}[1] . "$key" . ']\ ' . "\n\n");
		}
		&OutDesc($optl,$idt + 4, $desc, $descformat);
	  } else {
		&TexOut($optl,$idt, $PLSD{$code}{$descformat}[1] . "$key" . $PLSD{$code}{$descformat}[2] . "\n");
		&TexOut($optl,$idt + 4, $desc);
	  }
	}
	&TexOut($optl,$idt, $PLSD{$code}{$descformat}[3]);
}

###############################################################################
#
#  GET INFO FROM COMMENT BLOCK (CB)
#
###############################################################################

#
#    GetFlowTable --- Create associate map to CB-Title to Flow
#	In:
#	    $file	filename to be analyzed
#	Out:
#	    $table_ref	Output table
#	return:
#	    List of the header comment. This is ordered as it is in the file.
#
sub GetFlowTable {
    my($optl,$file, $table_ref, $cb_table_ref) = @_;
    my($cb);
    my($cb_head);
    my(@cb_flow) = ();
    my($head_title);

    #
    #   Output file header
    #     We take first //@ comment as the file header
    #
    foreach $cb (&CommentBlock::GetCommentBlocks($optl,$file)) {
	if ($cb->{mark} eq '@') {
	    if (scalar(@cb_flow) > 0) {
		$head_title = &Title2Key($cb_head->{title_pair}->GetKey(0));
		#warn "$head_title\n";
		#&PrintCB(join("\n", @cb_flow), "GFT> ");
		#&PrintCB($cb_head, "GFT> ");
		if (defined($$table_ref{$head_title})) {
		    &Bye(1, "$file: $cb_head->{title_pair}->GetKey(): two flows for the same title\n");
		}
		$$cb_table_ref{$head_title} = $cb_head;
		$$table_ref{$head_title} = &GetFlow($optl,@cb_flow);
		@cb_flow = ();
	    }
	    $cb_head = $cb;
	} elsif ($cb->{mark} eq '#') {
	    push(@cb_flow, $cb);
	} else {
	    &Bye(-1, "$file: Unkown comment mark \"$cb->{mark}\" .. bug?\n");
	}
    }
    if (scalar(@cb_flow) > 0) {
	$head_title = &Title2Key($cb_head->{title_pair}->GetKey(0));
	if (defined($$table_ref{$head_title})) {
	    my(@tmp) = &GetTitle($cb_head);
	    &Bye(1, "$file: $tmp[0]: two flows for the same title\n");
	}
	$$table_ref{$head_title} = &GetFlow($optl,@cb_flow);
    }
    #
    #	We can send adding descriptions  PENDING
    #
}

#
#   GetFlow --- Output //# comments as block flow chart
#
sub GetFlow {
    my($optl) = shift;
    my(@cblist) = @_;
    my($cb);
    my(@indent_list);
    my(%depth);
    my($max_depth);
    my($i);
    my($cb_idx);
    my($d);
    my($flow) = '';
    my($hline);
    my(%cpp_defs_hash);
    my(@cpp_defs_array);
    my($cpp_defs_idx) = 1;

    foreach $cb (@cblist) {
	  $i = $cb->{base_indent};
	  push(@indent_list, $i);
	  $depth{$i} = 1;
    }
    $d = 0;
    foreach $i (sort {$a <=> $b} keys %depth) {
	  $depth{$i} = $d++;
    }
    $max_depth = $d;
    $hline = '\cline{1-' . $max_depth . '}';

    #
    # Sanity Check of the uniq indentation
    #
    if ($max_depth * $optl->{'TableStep'}{'string'} > 0.5) {
	  warn "Warning: Too deep indentation $max_depth\n";
    }
    if ($#indent_list >= 1) {
	#my($base_indent) = 0;
	#for ($i = 1; $i <= $#indent_list; $i++) {
	#    if (abs($indent_list[$i] - $indent_list[$i - 1]) > 0) {
	#	$base_indent = abs($indent_list[$i] - $indent_list[$i - 1]);
	#	last;
	#    }
	#}
	#my($tmp_indent);
	#for ($i = 2; $i <= $#cblist; $i++) {
	#    $tmp_indent = abs($indent_list[$i] - $indent_list[$i - 1]);
	#    if ($tmp_indent != $base_indent && $tmp_indent != 0) {
	#	warn join(',', @indent_list) . "\n";
	#	warn "Warning: Un-unique indentation (!= $base_indent)\n";
	#    }
	#}
    }
    #
    #	Output Table Header
    #
    #my($line) = '\begin{center}\begin{tabular}{|';
    #my($line) = '\begin{tabular}{|';
    my($line) = '\scriptsize' . "\n" . '\begin{tabular}{|';
    for ($i = 0; $i < $max_depth; $i++) {
	  $line .= sprintf('p{%fcm}|', $optl->{'TableStep'}{'string'} * $optl->{'TableAbsLen'}{'string'});
    }
    $line .= "l}\n";	# one more entry for the CPP mark
    $flow .= $line;
    $flow .= "$hline\n";
    #
    #   Output table contents
    #
    for ($cb_idx = 0; $cb_idx <= $#cblist; $cb_idx++) {
	  #print "INDENT: $indent_list[$cb_idx]\n";
	  #print "DEPTH:  " . $depth{$indent_list[$cb_idx]} . "\n";
	  $d = $depth{$indent_list[$cb_idx]};
	  if ($d == 0) {
	    if ($cb_idx != 0) { # avoid double \hline in the beginning
		  $flow .= "$hline\n";
	    }
	  } else {
	    $flow .= sprintf('\cline{%d-%d}', $d + 1, $max_depth) . "\n";
	    for ($i = 0; $i < $d; $i++) {
		  $flow .= ' & '
	    }
	    $flow .="\n";

	  }
	  #$flow .= sprintf('\multicolumn{%d}{|p{%f\textwidth}|}{', $max_depth - $d, $TableScale - $TableStep * $d) . "\n";
	  $flow .= sprintf('\multicolumn{%d}{|p{%fcm}|}{', $max_depth - $d,
					   ($optl->{'TableScale'}{'string'} - $optl->{'TableStep'}{'string'} * $d) * $optl->{'TableAbsLen'}{'string'}) 
		. "\n";
	  #
	  # Actual contents
	  #
	  #$cblist[$cb_idx] =~ s/^(\s*)#/$1 /;
	  $flow .= $cblist[$cb_idx]->{text};

	  #
	  # End of line
	  #
	  $flow .= '}';
	  if ($cblist[$cb_idx]->{cpp_status} ne '') {
	    if (!defined($cpp_defs_hash{$cblist[$cb_idx]->{cpp_status}})) {
		  $cpp_defs_hash{$cblist[$cb_idx]->{cpp_status}} = $cpp_defs_idx;
		  $cpp_defs_array[$cpp_defs_idx] = $cblist[$cb_idx]->{cpp_status};
		  ++$cpp_defs_idx;
	    }
	    $flow .= '& *' . $cpp_defs_hash{$cblist[$cb_idx]->{cpp_status}};
	    #warn "TEXT: " . $cblist[$cb_idx]->{text};
	    #warn "CPP : " . $cblist[$cb_idx]->{cpp_status} . "\n";
	  }
	  $flow .= '\\\\' . "\n";
    }
    $flow .= "$hline\n";
    #
    # Print CPP status info if needed
    #
    for (my $i = 1; $i < $cpp_defs_idx; $i++) {
	  $flow .= '\\' . "multicolumn{$max_depth}{l}{"
		. "*$i Executed only if "
	      . &EscapeCharacters($optl,$cpp_defs_array[$i])
			. " is true.\n" . '}\\\\' . "\n";
    }

    #$flow .= '\end{tabular}\end{center}' . "\n";
    $flow .= '\end{tabular}' . "\n" . '\normalsize' . "\n";
	
    #print "depth = $max_depth\n";
    #print join(',', @indent_list) . "\n";
	
    $flow;
}

#
#
#   Title2Key --- Pickup tile as a keyword
#	In:
#	    $cb		comment block
#	return:
#	    Modified title as a keyword
#	NOTE:
#	    Title means the first key of the comment
#
sub Title2Key {
    my $title = shift;

    #warn "Title2Key: IN: $title\n";
#    if (!defined($title)) {
#	return "__UNDEF_TITLE__";
#    }
    $title =~ s/^\s*[^\s:(]+::(.*\()/$1/g;
    $title =~ s/\s*[^\s:(]+::(.*\()/$1/g;
    $title =~ s/\s*//g;
    #warn "Title2Key:OUT: $title\n";

    $title;
}

###############################################################################
#
#  TEX RELATED PROCEDURES
#
###############################################################################
sub TexOut {
    my($optl, $idt, $msg) = @_;
    my(@lines) = split("\n", $msg);
    return if ($#lines < 0);

    my($msg_idt) = &CommentBlock::CountIndent($optl,$lines[0]);
    my($sp) = &Blank($idt);
    my($line_idt);
    my($line);
    my($extra_idt);

    foreach $line (@lines) {
	  $line_idt = &CommentBlock::CountIndent($optl,$line);
	  $extra_idt = $line_idt - $msg_idt;
	  $line =~ s/^\s+//;
	  print $sp . &Blank($extra_idt) . "$line\n";
	  $optl->{'debug'}{'Do'} and warn "Out:". $sp . &Blank($extra_idt) . "$line\n";
    }
}

#
#  EscapeCharacters
#
sub EscapeCharacters {
    my($optl,$cb) = @_;
    my(@lines) = split($optl->{'EscapeKey'}{'string'}, $cb);

    for (my($i) = 0; $i <= $#lines; $i +=2) {
	$lines[$i] =~ s/[\#\$%&_]/\\$&/g;
	$lines[$i] =~ s/[<>]/\$$&\$/g;
	$lines[$i] =~ s/~/\\~{ }/g;
	$lines[$i] =~ s/\[/\$\\mbox{[}\$/g;
	$lines[$i] =~ s/\]/\$\\mbox{]}\$/g;
	$lines[$i] =~ s/\*/\\protect{\$*\$}/g;
    }
    return join('', @lines);
}
#
###############################################################################
#
#  TEXT TOOLS
#
###############################################################################
#
#   Blank --- create blank of specified length
#	In:
#	    len		length of the blank
#
sub Blank {
    my($len) = @_;
    my($b) = '';

    while (--$len >= 0) {
	$b .= ' ';
    }
    $b;
}




#
#
#   RemoveFollowingBlanks --- Remove spaces and tabs at the end of the line
#	In:
#	    $str	string to be processed
#	return:
#	    processed string
#
sub RemoveFollowingBlanks {
    my($str) = @_;
    my(@lines) = split("\n", $str);
    my($i);

    for ($i = $#lines; $i >= 0; $i--) {
	last if ($lines[$i] !~ m/^[\s]*$/);
    }
    ($i < 0) ? '' : join("\n", @lines[0..$i]);
}

###############################################################################
#
#  TOOLS
#
###############################################################################
sub Bye {
    warn "$_[1]\n";
    exit($_[0]);
}

exargs::exargs();
&Main;
